#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h> 
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <asm-generic/gpio.h>
#include <linux/uaccess.h>


#define 	    	    	Major 		0           /* 不为0的时候则手动设置主设备号 */
#define                     gpioLedCNT  1           /* 设备的数量 */
#define                     LEDNAME     "gpioled"   /* 设备的名字 */

/* 定义一个设备结构体 */
struct gpioled_dev{
    dev_t devid;                        /* 设备号 */
    int major;                          /* 主设备号 */
    int minor;                          /* 从设备号 */
    struct cdev cdev;
    struct class * class;
    struct device * device;
    struct device_node * nd;           /*   设备树节点  */
    int rgb_led_red;                   /*   通过名字获取到的引脚编号    */
    int rgb_led_green;
    int rgb_led_blue;

};

struct gpioled_dev gpioled;


/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件，file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int gpioled_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled;                      /*  设置私有数据    */
    return 0;
}


/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t gpioled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}


/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件，表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数，如果为负值，表示写入失败
 */
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    unsigned char databuf[1];                                       /*  存放三个字母：RGB，对应不同颜色的灯  */
    unsigned char color;
    struct gpioled_dev *mydev = filp->private_data;               /*  在open函数中已经区定了指向，在这里获取设备的私有数据    */
    
    retvalue = copy_from_user(databuf , buf , cnt);              /*  与应用程序对接，读多少数据不用管  */
    if(retvalue < 0){
        printk(KERN_EMERG"\r\n write data failed !\r\n");
        return -EFAULT;                                         /*  错误返回值专属  */
    }
    color = databuf[0];
    if(color == 'R'){                                           /*  不直接采用结构体而是使用私有数据的指针，避免出现语法或者逻辑错误    */
        gpio_direction_output(mydev->rgb_led_red,0);
        gpio_direction_output(mydev->rgb_led_green,1);
        gpio_direction_output(mydev->rgb_led_blue,1);
        printk(KERN_EMERG"\r\n open the red led \r\n");
    }else if(color == 'G'){
        gpio_direction_output(mydev->rgb_led_red,1);
        gpio_direction_output(mydev->rgb_led_green,0);
        gpio_direction_output(mydev->rgb_led_blue,1);
        printk(KERN_EMERG"\r\n open the green led \r\n");
    }else if (color == 'B')
    {
        gpio_direction_output(mydev->rgb_led_red,1);
        gpio_direction_output(mydev->rgb_led_green,1);
        gpio_direction_output(mydev->rgb_led_blue,0);
        printk(KERN_EMERG"\r\n open the blue led \r\n");
    }
    
    return retvalue;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int gpioled_release(struct inode *inode, struct file *filp)
{
    return 0;
}


/* 设备操作结构体 */

static const struct file_operations   gpioled_flops = {
    .owner =   THIS_MODULE,
    .write =   gpioled_write,
    .read =    gpioled_read,
    .open =    gpioled_open,
    .release = gpioled_release,
};


/* 驱动入口函数 */
static int __init gpioLed_Init( void )
{
    gpioled.devid = Major;

    /* 1.创建设备号 */
    if(gpioled.devid) {                     /*指定设备号*/
        register_chrdev_region(gpioled.devid , gpioLedCNT , LEDNAME);
    } else{                                 /* 向系统申请设备号 */
        alloc_chrdev_region(&gpioled.devid , 0 , gpioLedCNT , LEDNAME);
        gpioled.major=MAJOR(gpioled.devid);             /* 储存主从设备号 */
        gpioled.minor=MINOR(gpioled.major);
    }
    printk(KERN_EMERG"ledgpio major =%d ,minor =%d \r\n",gpioled.major ,gpioled.minor );

    /* 2.创建cdev */
    gpioled.cdev.owner =   THIS_MODULE;
    cdev_init( &gpioled.cdev , & gpioled_flops);
    cdev_add( &gpioled.cdev, gpioled.devid , gpioLedCNT);

    /* 创建类 */
    gpioled.class = class_create( THIS_MODULE , LEDNAME);
    if(IS_ERR( gpioled.class )){
        return PTR_ERR( gpioled.class );
    }

    /* 创建设备 */
    gpioled.device =  device_create( gpioled.class ,NULL ,gpioled.devid , NULL ,LEDNAME );
    if(IS_ERR( gpioled.device )){
        return PTR_ERR( gpioled.device );
    }

    /*  设备创建完毕，可以正式开始操作gpio*/
    /*  获取设备树节点  */
    gpioled.nd=of_find_node_by_path("/RGB");
    if(gpioled.nd == NULL){
        printk(KERN_EMERG"\r\n get rgb failed \r\n");
    }

    /*  获取引脚的编号  */
    gpioled.rgb_led_red = of_get_named_gpio(gpioled.nd,"rgb_led_red",0);
    gpioled.rgb_led_blue = of_get_named_gpio(gpioled.nd,"rgb_led_blue",0);
    gpioled.rgb_led_green = of_get_named_gpio(gpioled.nd,"rgb_led_green",0);
    printk(KERN_EMERG"\r\n the gpio num is %d ,%d ,%d\r\n", gpioled.rgb_led_red, gpioled.rgb_led_blue, gpioled.rgb_led_green);

    /*  申请使用gpio    */
    int ret=0;
    ret = gpio_request(gpioled.rgb_led_red , NULL);
    if(ret < 0){
        printk(KERN_EMERG"\r\n request rgb_led_red failed ,check out the device tree \r\n");
    }
    ret = gpio_request(gpioled.rgb_led_blue , NULL);
    if(ret < 0){
        printk(KERN_EMERG"\r\n request rgb_led_blue failed ,check out the device tree \r\n");
    }
    ret = gpio_request(gpioled.rgb_led_green , NULL);
    if(ret < 0){
        printk(KERN_EMERG"\r\n request rgb_led_green failed ,check out the device tree \r\n");
    }

    /*  设置gpio初始的输出电平 */
    gpio_direction_output(gpioled.rgb_led_red,0);
    gpio_direction_output(gpioled.rgb_led_blue,0);
    gpio_direction_output(gpioled.rgb_led_green,0);

    return 0;
}

/* 驱动出口函数 */
static void __exit gpioLed_Exit( void )
{
    printk(KERN_EMERG"\r\n exit this moudule! \r\n");
    /*   把rgb灯关掉 ,高电平有效    */
    gpio_direction_output(gpioled.rgb_led_red,1);
    gpio_direction_output(gpioled.rgb_led_blue,1);
    gpio_direction_output(gpioled.rgb_led_green,1);
    /*  释放申请的引脚  */
    gpio_free(gpioled.rgb_led_red);
    gpio_free(gpioled.rgb_led_blue);
    gpio_free(gpioled.rgb_led_green);

    /* 
        注销字符设备驱动
        注意：注销字符设备一定要按照顺序来，后创建的先注销
     */
    cdev_del(&gpioled.cdev);                                            /* 注销cdev */
    unregister_chrdev_region(gpioled.devid,gpioLedCNT);                 /* 注销设备号 */            
    device_destroy(gpioled.class,gpioled.devid);                        /* 注销device */
    class_destroy(gpioled.class);                                       /* 注销类 */
}




/* 注册驱动入口函数 */
module_init(gpioLed_Init);
module_exit(gpioLed_Exit);
MODULE_AUTHOR("hanyang");
MODULE_LICENSE("GPL");



